#pragma once

#include <thrust/set_operations.h>
#include <thrust/sort.h>

template <typename Container1,
          typename Container2,
          typename Container3>
struct SetDifference
{
  Container1 A;
  Container2 B;
  Container3 C;

  template <typename Range1, typename Range2, typename Range3>
  SetDifference(const Range1& X, const Range2& Y, const Range3& Z)
    : A(X.begin(), X.end()),
      B(Y.begin(), Y.end()),
      C(Z.begin(), Z.end())
  {
    thrust::stable_sort(A.begin(), A.end());
    thrust::stable_sort(B.begin(), B.end());
  }

  void operator()(void)
  {
    thrust::set_difference(A.begin(), A.end(), B.begin(), B.end(), C.begin());
  }
  
  void reset(void)
  {
    // nothing to do
  }
};

template <typename Container1,
          typename Container2,
          typename Container3,
          typename Compare>
struct SetDifferenceComp
{
  Container1 A;
  Container2 B;
  Container3 C;
  Compare comp;

  template <typename Range1, typename Range2, typename Range3>
  SetDifferenceComp(const Range1& X, const Range2& Y, const Range3& Z, Compare comp)
    : A(X.begin(), X.end()),
      B(Y.begin(), Y.end()),
      C(Z.begin(), Z.end()),
      comp(comp)
  {
    thrust::stable_sort(A.begin(), A.end(), comp);
    thrust::stable_sort(B.begin(), B.end(), comp);
  }

  void operator()(void)
  {
    thrust::set_difference(A.begin(), A.end(), B.begin(), B.end(), C.begin(), comp);
  }
  
  void reset(void)
  {
    // nothing to do
  }
};

template <typename Container1,
          typename Container2,
          typename Container3>
struct SetIntersection
{
  Container1 A;
  Container2 B;
  Container3 C;

  template <typename Range1, typename Range2, typename Range3>
  SetIntersection(const Range1& X, const Range2& Y, const Range3& Z)
    : A(X.begin(), X.end()),
      B(Y.begin(), Y.end()),
      C(Z.begin(), Z.end())
  {
    thrust::stable_sort(A.begin(), A.end());
    thrust::stable_sort(B.begin(), B.end());
  }

  void operator()(void)
  {
    thrust::set_intersection(A.begin(), A.end(), B.begin(), B.end(), C.begin());
  }
  
  void reset(void)
  {
    // nothing to do
  }
};

template <typename Container1,
          typename Container2,
          typename Container3,
          typename Compare>
struct SetIntersectionComp
{
  Container1 A;
  Container2 B;
  Container3 C;
  Compare comp;

  template <typename Range1, typename Range2, typename Range3>
  SetIntersectionComp(const Range1& X, const Range2& Y, const Range3& Z, Compare comp)
    : A(X.begin(), X.end()),
      B(Y.begin(), Y.end()),
      C(Z.begin(), Z.end()),
      comp(comp)
  {
    thrust::stable_sort(A.begin(), A.end(), comp);
    thrust::stable_sort(B.begin(), B.end(), comp);
  }

  void operator()(void)
  {
    thrust::set_intersection(A.begin(), A.end(), B.begin(), B.end(), C.begin(), comp);
  }
  
  void reset(void)
  {
    // nothing to do
  }
};

template <typename Container1,
          typename Container2,
          typename Container3>
struct SetSymmetricDifference
{
  Container1 A;
  Container2 B;
  Container3 C;

  template <typename Range1, typename Range2, typename Range3>
  SetSymmetricDifference(const Range1& X, const Range2& Y, const Range3& Z)
    : A(X.begin(), X.end()),
      B(Y.begin(), Y.end()),
      C(Z.begin(), Z.end())
  {
    thrust::stable_sort(A.begin(), A.end());
    thrust::stable_sort(B.begin(), B.end());
  }

  void operator()(void)
  {
    thrust::set_symmetric_difference(A.begin(), A.end(), B.begin(), B.end(), C.begin());
  }
  
  void reset(void)
  {
    // nothing to do
  }
};

template <typename Container1,
          typename Container2,
          typename Container3,
          typename Compare>
struct SetSymmetricDifferenceComp
{
  Container1 A;
  Container2 B;
  Container3 C;
  Compare comp;

  template <typename Range1, typename Range2, typename Range3>
  SetSymmetricDifferenceComp(const Range1& X, const Range2& Y, const Range3& Z, Compare comp)
    : A(X.begin(), X.end()),
      B(Y.begin(), Y.end()),
      C(Z.begin(), Z.end()),
      comp(comp)
  {
    thrust::stable_sort(A.begin(), A.end(), comp);
    thrust::stable_sort(B.begin(), B.end(), comp);
  }

  void operator()(void)
  {
    thrust::set_symmetric_difference(A.begin(), A.end(), B.begin(), B.end(), C.begin(), comp);
  }
  
  void reset(void)
  {
    // nothing to do
  }
};

template <typename Container1,
          typename Container2,
          typename Container3>
struct SetUnion
{
  Container1 A;
  Container2 B;
  Container3 C;

  template <typename Range1, typename Range2, typename Range3>
  SetUnion(const Range1& X, const Range2& Y, const Range3& Z)
    : A(X.begin(), X.end()),
      B(Y.begin(), Y.end()),
      C(Z.begin(), Z.end())
  {
    thrust::stable_sort(A.begin(), A.end());
    thrust::stable_sort(B.begin(), B.end());
  }

  void operator()(void)
  {
    thrust::set_union(A.begin(), A.end(), B.begin(), B.end(), C.begin());
  }
  
  void reset(void)
  {
    // nothing to do
  }
};

template <typename Container1,
          typename Container2,
          typename Container3,
          typename Compare>
struct SetUnionComp
{
  Container1 A;
  Container2 B;
  Container3 C;
  Compare comp;

  template <typename Range1, typename Range2, typename Range3>
  SetUnionComp(const Range1& X, const Range2& Y, const Range3& Z, Compare comp)
    : A(X.begin(), X.end()),
      B(Y.begin(), Y.end()),
      C(Z.begin(), Z.end()),
      comp(comp)
  {
    thrust::stable_sort(A.begin(), A.end(), comp);
    thrust::stable_sort(B.begin(), B.end(), comp);
  }

  void operator()(void)
  {
    thrust::set_union(A.begin(), A.end(), B.begin(), B.end(), C.begin(), comp);
  }
  
  void reset(void)
  {
    // nothing to do
  }
};

